package cn.net.dpi.wlqc.server.server;

import cn.hutool.core.date.DateUtil;
import cn.net.dpi.wlqc.server.action.ECarPictureUploadAction;
import cn.net.dpi.wlqc.server.action.ECarRegisterAction;
import cn.net.dpi.wlqc.server.action.ECarTrackUploadAction;
import cn.net.dpi.wlqc.server.handler.ActionHandler;
import cn.net.dpi.wlqc.server.proto.ThirdTrackBatch;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * LoServer starter<br>
 * 用于启动服务器的主对象<br>
 * 使用LoServer.start()启动服务器<br>
 * 服务的Action类和端口等设置在ServerSetting中设置
 *
 * @author Looly
 */
@Slf4j
@Component
public class NettyServer {

    @Value("${server.netty.port:8090}")
    int port;

    /**
     * 启动服务器
     */
    @PostConstruct
    public void start() {
        ServerSetting.setAction("/ecar/track/register", ECarRegisterAction.class);
        ServerSetting.setAction("/ecar/track/upload", ECarTrackUploadAction.class);
        ServerSetting.setAction("/ecar/picture/upload", ECarPictureUploadAction.class);
        ServerSetting.setRoot("root");
        ServerSetting.setPort(port);
        start(ServerSetting.getPort());
    }

    /**
     * 启动服务
     *
     * @param port 端口
     */
    public void start(int port) {
        long start = System.currentTimeMillis();

        // Configure the server.
        //BossGroup（主Reactor）：单线程处理连接请求（accept）
        //BossGroup工作流程‌: 轮询accept事件 → 处理连接 → 将Channel注册到WorkerGroup
        final EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        //WorkerGroup（子Reactor）：多线程处理已连接通道的I/O
        //WorkerGroup工作流程‌: 轮询read/write事件 → 触发Pipeline处理 → 执行异步任务
        final EventLoopGroup workerGroup = new NioEventLoopGroup();

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(bossGroup, workerGroup)
                .option(ChannelOption.SO_BACKLOG, 1024)
                .channel(NioServerSocketChannel.class)
//				.handler(new LoggingHandler(LogLevel.INFO))
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline()
                                .addLast(new LoggingHandler(LogLevel.INFO))
                                .addLast(new HttpServerCodec())
                                // 把多个消息转换为一个单一的FullHttpRequest或是FullHttpResponse
                                .addLast(new HttpObjectAggregator(65536))
                                // 压缩Http消息
                                //.addLast(new HttpChunkContentCompressor())
                                // 大文件支持
                                .addLast(new ChunkedWriteHandler())
                                // 解码器部分
                                // 指定 Protobuf 消息类型
                                .addLast(new ProtobufDecoder(ThirdTrackBatch.ThirdTrackReq.getDefaultInstance()))
                                // 编码器部分
                                // 将 Protobuf 对象编码为字节流
                                .addLast(new ProtobufEncoder())
                                // 自定义处理逻辑
                                .addLast(new ActionHandler());
                    }
                });
        serverBootstrap.bind(port).addListener(future -> {
            if (future.isSuccess()) {
                log.info("***** Welcome To LoServer on port [{}], starting spend {}ms *****", port, DateUtil.spendMs(start));
            } else {
                log.info("端口[{}]绑定失败!", port);
            }
        });
    }
}
